﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using UnityEngine;

/// <summary>
/// UpdateManager的状态,可重新自定义
/// </summary>
public enum UpdateState
{
    CheckExtract,
    Extracting,
    FinishExtract,
    CheckUpdate,
    SelectUpdate,
    Updating,
    FinishUpdate,
}

/// <summary>
/// 备注：tmoon
/// TODO:1.对异步操作完善错误处理
///      2.使用其他异步方法获取下载速度
///      3.怀疑卡帧痛点在远程WWW(files.txt)这里
/// </summary>
public class UpdateManager : MonoBehaviour
{
    public long UpdateTotal { get; private set; } = 0;
    public long UpdateAmount { get; private set; } = 0;
    public long ExtractedTotal { get; private set; } = 0;
    public long ExtractedAmount { get; private set; } = 0;
    public UpdateState UpdateState { get; private set; } = UpdateState.CheckExtract;
    /// <summary>
    /// 用户界面选择是否更新
    /// </summary>
    public bool IsUpdate { get; set; } = false;
    /// <summary>
    /// 更新完成后处理的事件
    /// </summary>
    public event Action OnFinishUpdateEvent;
    public static UpdateManager Instance { get; private set; }

    /// <summary>
    /// 存储需要更新的资源的文件列表
    /// 远程文件，本地文件，文件大小
    /// </summary>
    private Dictionary<string, Tuple<string, long>> updateFiles = new Dictionary<string, Tuple<string, long>>();
    private List<Task> extractedTasks = new List<Task>();
    private WebClient webClient = null;

    private void Awake()
    {
        Instance = this;

        OnFinishUpdateEvent += () =>
        {
            Destroy(this.gameObject);
            GC.Collect();
        };

        // 检查资源
        CheckExtractResource();
    }

    private void CheckExtractResource()
    {
        UpdateState = UpdateState.CheckExtract;

        // 判断本地是否已有下载或解压了资源
        bool isExists = Directory.Exists(Util.DataPath)
                        && Directory.Exists(Util.DataPath + "lua/")
                        && File.Exists(Util.DataPath + "files.txt");

        // 本地已有资源则直接更新资源
        if (isExists || AppConst.DebugMode)
        {
            Debug.Log("本地已有资源，直接与文件服务器对比更新资源!");
            StartCoroutine(CheckUpdateResource());
            return;
        }

        Debug.Log("本地没有资源,直接从游戏安装包的StreamingAssets文件夹解压资源到本地!");
        StartCoroutine(OnExtractResource());
    }

    private IEnumerator OnExtractResource()
    {
        UpdateState = UpdateState.Extracting;

        // 数据目录
        string dataPath = Util.DataPath;
        // 游戏包资源目录
        string resPath = Util.AppContentPath();

        if (Directory.Exists(dataPath))
        {
            Directory.Delete(dataPath, true);
        }
        Directory.CreateDirectory(dataPath);

        string outfile = dataPath + "files.txt";
        string infile = resPath + "files.txt";

        Debug.Log("正在解压主文件:>" + infile);
        if (Application.platform == RuntimePlatform.Android)
        {
            using (WWW www = new WWW(infile))
            {
                yield return www;

                if (www.isDone)
                {
                    File.WriteAllBytes(outfile,www.bytes);
                }
            }
        }
        else
        {
            File.Copy(infile, outfile);
        }

        yield return new WaitForEndOfFrame();

        string[] files = File.ReadAllLines(outfile);
        foreach (var file in files)
        {
            ExtractedTotal += long.Parse(file.Split('|')[2]);
        }
        Debug.Log("需要解压资源的总数量：" + ExtractedTotal);

        yield return new WaitForEndOfFrame();

        // 解压所有文件到数据目录
        foreach (var file in files)
        {
            string[] fs = file.Split('|');
            infile = resPath + fs[0];
            outfile = dataPath + fs[0];

            Debug.Log("正在解压文件:>" + infile);

            string dir = Path.GetDirectoryName(outfile);

            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }

            if (Application.platform == RuntimePlatform.Android)
            {
                using (WWW www = new WWW(infile))
                {
                    yield return www;

                    if (www.isDone)
                    {
                        extractedTasks.Add(ExtractFileAsync(www.bytes, outfile));
                    }
                }
            }
            else
            {
                extractedTasks.Add(ExtractFileAsync(infile, outfile));
            }

            yield return new WaitForEndOfFrame();
        }

        Task task = Task.WhenAll(extractedTasks.ToArray());

        while (!task.IsCompleted)
        {
            yield return new WaitForEndOfFrame();
        }

        Debug.Log("解压完成!!!");
        extractedTasks.Clear();
        UpdateState = UpdateState.FinishExtract;
        // 解压完成，开始启动检查更新资源
        StartCoroutine(CheckUpdateResource());
    }

    private IEnumerator CheckUpdateResource()
    {
        UpdateState = UpdateState.CheckUpdate;

        yield return new WaitForChangedResult();

        if (!AppConst.UpdateMode)
        {
            // 初始化项目
            UpdateState = UpdateState.FinishUpdate;
            OnFinishUpdateEvent?.Invoke();
            yield break;
        }

        string dataPath = Util.DataPath;
        string url = AppConst.WebUrl;
        string random = DateTime.Now.ToString("yyyymmddhhmmss");
        string listUrl = url + "files.txt?v=" + random;
        Debug.LogWarning("LoadUpdate---->>>" + listUrl);

        string filesText;
        using (WWW www = new WWW(listUrl))
        {
            yield return www;

            if (www.error != null)
            {
                // 主文件下载出错，更新失败
                Debug.Log("主文件下载出错，更新失败，错误信息为：" + www.error);
                yield break;
            }

            yield return new WaitForEndOfFrame();

            File.WriteAllBytes(dataPath + "files.txt", www.bytes);
            filesText = www.text;
        }

        yield return new WaitForEndOfFrame();

        // 对比文件md5值检查需要更新的资源
        string[] files = filesText.Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

        for (int i = 0; i < files.Length; i++)
        {
            string[] keyValue = files[i].Split('|');
            string f = keyValue[0];
            string localfile = (dataPath + f).Trim();
            string path = Path.GetDirectoryName(localfile);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            string fileUrl = url + f + "?v=" + random;
            bool canUpdate = !File.Exists(localfile);
            if (!canUpdate)
            {
                string remoteMd5 = keyValue[1].Trim();
                string localMd5 = Util.GetMD5File(localfile);
                canUpdate = !remoteMd5.Equals(localMd5);
            }

            yield return new WaitForEndOfFrame();

            if (canUpdate)
            {
                // 本地缺少文件
                Debug.Log("本地需要更新的文件>>" + fileUrl);

                long updateAmount = long.Parse(keyValue[2]);
                UpdateTotal += updateAmount;
                updateFiles.Add(fileUrl, new Tuple<string, long>(localfile, updateAmount));
            }

            yield return new WaitForEndOfFrame();
        }

        Debug.Log("更新资源大小：" + UpdateTotal);

        if (UpdateTotal > 0 )
        {
            UpdateState = UpdateState.SelectUpdate;
            while (!IsUpdate)
            {
                yield return new WaitForEndOfFrame();
            }
            OnUpdateResourceAsync();
        }
        else
        {
            UpdateState = UpdateState.FinishUpdate;
            OnFinishUpdateEvent?.Invoke();
            Debug.Log("已经是最新版本了！");
        }
    }

    private async Task OnUpdateResourceAsync()
    {
        UpdateState = UpdateState.Updating;
        using (webClient = new WebClient())
        {
            foreach (var item in updateFiles)
            {
                await webClient.DownloadFileTaskAsync(item.Key, item.Value.Item1);
                UpdateAmount += item.Value.Item2;
            }

            updateFiles.Clear();
        }
        Debug.Log("更新完成!!!");
        UpdateState = UpdateState.FinishUpdate;
        OnFinishUpdateEvent?.Invoke();
    }

    private async Task ExtractFileAsync(byte[] fromBytes, string toPath)
    {
        using (FileStream fileStream = new FileStream(toPath, FileMode.Create))
        {
            await fileStream.WriteAsync(fromBytes, 0, fromBytes.Length);
            ExtractedAmount += fileStream.Length;
        }
    }

    private async Task ExtractFileAsync(string fromPath, string toPath)
    {
        using (FileStream toStream = new FileStream(toPath, FileMode.Create))
        {
            using (FileStream fromStream = new FileStream(fromPath, FileMode.Open))
            {
                await fromStream.CopyToAsync(toStream);
                ExtractedAmount += toStream.Length;
            }
        }
    }

}
